--- HTTP
-- @classmod route.app
local skynet = require 'skynet'
local urllib = require "http.url"
local template = require "template"

local content_type_header = require 'mime_types'
local multipart = require "multipart"

local table  = table
local string = string
local fnlib  = require "fnlib"
local cjson   = require "cjson"

local ROUTERS, PREFIX = {}, {}
local VIEW, GET, POST, PUT, DELETE = {}, {}, {}, {}, {}

-- match path in reverse order
--
-- '/usr/local/bin/lua53' =>
--
-- {
--  '/usr/local/bin/',
--  '/usr/local/',
--  '/usr/',
--  '/',
-- }
local function check_prefix(path)
  local paths = {}
  for pos in path:gmatch('()/') do
    table.insert(paths, path:sub(1, pos))
  end

  for i = #paths, 1, -1 do
    local mp = paths[i]
    local hd = PREFIX[mp]
    if hd then
      return hd, mp, path:sub(#mp + 1)
    end
  end
end

--- 路由顺序
-- 根据路径匹配view
-- 匹配http方法下的路径
-- 匹配普通路由
-- 匹配路径前缀
local function dispatch(method, url, header, body)
  local path, query = urllib.parse(url)
  query = query and urllib.parse_query(query) or nil
  local view_info = VIEW[path]

  if view_info then
    local view_filename = view_info[1]
    local f = io.open(view_filename, 'rb')
    if not f then
      return 404, 'view not found'
    end
    f:close()
    local hd = content_type_header(view_filename)
    return 200, template.compile(view_filename)(view_info[2]), hd
  end

  local dict, handler
  if method == 'GET' then
    dict = GET
  elseif method == 'POST' then
    dict = POST
  elseif method == 'PUT' then
    dict = PUT
  elseif method == 'DELETE' then
    dict = DELETE
  end

  handler = dict and dict[path] or ROUTERS[path]

  local header_content_type = header['content-type']
  if header_content_type == 'application/json' then
    body = cjson.decode(body)
  end

  if handler then
    local status, reason_phrase, rsp  =  handler(method, query, body)
    if rsp then
      rsp["Access-Control-Allow-Origin"] = "*"
    else
      rsp = {}
      rsp["Access-Control-Allow-Origin"] = "*"
    end
    
    return status, reason_phrase, rsp
  else
    local mpath, filename
    handler, mpath, filename = check_prefix(path)
    if handler then
      return handler(mpath, filename, query, body)
    else
      return 404, '404', {
        ['Content-Type'] = 'text/html',
        ['Server'] = 'skynet/0.0.1 (CentOS)',
      }
    end
  end
end

local app = {}

function app.clear_all()
  fnlib.array_foreach( {PREFIX, ROUTERS, GET, POST, PUT, DELETE, VIEW}, fnlib.empty_table)
end

function app.set_view_cache(enable)
  template.caching(enable)
end

--- serve single file
-- @int status_code HTTP status code
-- @string filename file path
function app.response_file(code, filename)
  if dev_sys_log then
    skynet.error('serving file: ', filename)
  end
  local f = io.open(filename, 'rb')
  if f then
    return code, function() return f:read(1024 * 4) end, content_type_header(filename)
  else
    return 404, 'file not found'
  end
end

local function make_handler(tb)
  return function (path, func) tb[path] = func end
end

app.match  = make_handler(ROUTERS)

--- register a get handler
-- @function app.get
-- @string path uri
-- @func handler
app.get      = make_handler(GET)
app.post     = make_handler(POST)
app.put      = make_handler(PUT)
app.delete   = make_handler(DELETE)

function app.serve_view(path, filepath, env)
  VIEW[path] = { filepath, env}
end

local prefix = make_handler(PREFIX)

-- serve a virtual path
app.prefix = prefix

--- serve static directory
-- @string vpath path in http request
-- @string path target directory
-- @string[opt] default_file default file to serve
-- @usage app.serve_dir('/doc/', 'doc/build/')
function app.serve_dir(vpath, path, df)
  df = df or 'index.html'
  prefix(vpath,function (_, filename)
    filename = filename == '' and df or filename
    return app.response_file(200, path .. filename)
  end)
end

return {
  dispatch = dispatch,
  app      = app,
}
